use actix_web::{
    get, post,
    web::{self, Payload, Query, ServiceConfig},
    HttpRequest,
};
use serde::Deserialize;

use crate::{
    error::{new_err, AppErr, ErrWrap},
    web::{
        cfg::{CBOR_REQUEST_MAX_SIZE, REMOTE_CMD},
        codec::{
            device_session::{get_device_session, SharedDeviceState},
            event::{get_event_manager, Event},
            user_session::UserSession,
            Void,
        },
    },
};

#[get("poll")]
async fn on_poll(session: SharedDeviceState) -> Result<Event, AppErr> {
    let event = session.recv_event().await?;
    Ok(event)
}

#[get("ack")]
async fn on_ack(session: SharedDeviceState) -> Result<Void, AppErr> {
    session.clear_last_event().await?;
    Ok(Void)
}

#[post("result")]
async fn on_result(_: SharedDeviceState, evnet: Event) -> Result<Void, AppErr> {
    get_event_manager().send_result(evnet)?;
    Ok(Void)
}

#[derive(Debug, Deserialize)]
struct CtrlReq {
    id: i64,
}

#[post("req")]
async fn on_req(
    _: UserSession,
    id: Query<CtrlReq>,
    req: HttpRequest,
    body: Payload,
) -> Result<Event, AppErr> {
    let session = get_device_session(id.id)?;

    let body = body
        .to_bytes_limited(CBOR_REQUEST_MAX_SIZE)
        .await
        .wrap()?
        .wrap()?;

    let cmd: u8 = req
        .headers()
        .get(REMOTE_CMD)
        .ok_or(new_err("no cmd"))?
        .to_str()
        .wrap()?
        .parse()
        .wrap()?;

    let event = session.req(cmd, body).await?;

    Ok(event)
}

#[post("notify_ack")]
async fn on_notify_ack(
    _: UserSession,
    id: Query<CtrlReq>,
    req: HttpRequest,
    body: Payload,
) -> Result<Void, AppErr> {
    let session = get_device_session(id.id)?;

    let body = body
        .to_bytes_limited(CBOR_REQUEST_MAX_SIZE)
        .await
        .wrap()?
        .wrap()?;

    let cmd: u8 = req
        .headers()
        .get(REMOTE_CMD)
        .ok_or(new_err("no cmd"))?
        .to_str()
        .wrap()?
        .parse()
        .wrap()?;

    session.notify_ack(cmd, body).await?;

    Ok(Void)
}

#[post("notify")]
async fn on_notify(
    _: UserSession,
    id: Query<CtrlReq>,
    req: HttpRequest,
    body: Payload,
) -> Result<Void, AppErr> {
    let session = get_device_session(id.id)?;

    let body = body
        .to_bytes_limited(CBOR_REQUEST_MAX_SIZE)
        .await
        .wrap()?
        .wrap()?;

    let cmd: u8 = req
        .headers()
        .get(REMOTE_CMD)
        .ok_or(new_err("no cmd"))?
        .to_str()
        .wrap()?
        .parse()
        .wrap()?;

    session.notify(cmd, body).await?;

    Ok(Void)
}

pub fn register(cfg: &mut ServiceConfig) {
    let scope = web::scope("/remote")
        .service(on_poll)
        .service(on_ack)
        .service(on_result)
        .service(on_req)
        .service(on_notify_ack)
        .service(on_notify);
    cfg.service(scope);
}
